package com.luo.demo.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2PkceRefreshTokenGrantRequestEntityConverter;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.web.*;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Arrays;
import java.util.Collections;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * Spring Security配置
 *
 * @author luohq
 * @date 2022-02-18
 */
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http, OidcClientInitiatedLogoutSuccessHandler oidcClientInitiatedLogoutSuccessHandler) throws Exception {
        http

                .headers(headers -> headers
                        .frameOptions(frameOptions -> frameOptions
                                .disable()
                        )
                )
                .authorizeHttpRequests(authorizeRequests -> authorizeRequests
                        .mvcMatchers("/logout", "/front_logout").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(withDefaults())
                //.exceptionHandling(handling -> handling
                //
                //)
                //.oauth2Login(oAuth2LoginConfigurer -> oAuth2LoginConfigurer
                //                //自定义登录页uri
                //                //默认不配置则使用Security默认登录页（生成OAuth2 client登录链接）
                //                //若未配置且仅有唯一client配置，则默认使用/oauth2/authorization/{clientRegId}作为认证登录入口
                //                .loginPage("/login")
                //
                //                //========================================================================================================================
                //                //======================================= OAuth2AuthorizationRequestRedirectFilter =======================================
                //                //========================================================================================================================
                //                // 拦截/oauth2/authorization/{clientRegId}请求 - DefaultOAuth2AuthorizationRequestResolver
                //                // 根据clientRegId查询client注册信息并生成授权端点请求OAuth2AuthorizationRequest - DefaultOAuth2AuthorizationRequestResolver
                //                // 若为authorization_code模式则缓存OAuth2AuthorizationRequest到当前session - HttpSessionOAuth2AuthorizationRequestRepository
                //                // 最后重定向到AuthServer授权端点（OAuth2AuthorizationRequest.getAuthorizationRequestUri()）
                //                //========================================================================================================================
                //                //OAuth2授权端点相关配置
                //                .authorizationEndpoint(authorizationEndpointConfig -> authorizationEndpointConfig
                //                        //当前client触发OAuth2登录的基础URI
                //                        //具体请求当前client的/oauth2/authorization/{clientRegId}
                //                        //例如请求/oauth2/authorization/luo-oauth-client1则触发luo-oauth2-client1对应的OAuth2 Client登录（跳转到AuthServer授权端点）
                //                        //默认值/oauth2/authorization
                //                        .baseUri("/oauth2/authorization")
                //                        //授权请求存储仓库
                //                        //默认实现HttpSessionOAuth2AuthorizationRequestRepository
                //                        //即通过session存储当前请求对应的授权请求对象（若allowMultipleAuthorizationRequests则使用Map<state, AuthRequest形式>）
                //                        .authorizationRequestRepository(new HttpSessionOAuth2AuthorizationRequestRepository())
                //                        //从/oauth2/authorization/{clientRegId}请求解析OAuth2授权端点请求对象
                //                        //默认实现DefaultOAuth2AuthorizationRequestResolver
                //                        //.authorizationRequestResolver(new InMemoryClientRegistrationRepository(clientRegInfoList), "/oauth2/authorization"))
                //                )
                //
                //                //============================================================================================================================
                //                //======================================= OAuth2LoginAuthenticationFilter ====================================================
                //                //============================================================================================================================
                //                // 拦截/login/oauth2/code/{clientRegId}请求
                //                // 获取之前缓存到session中的OAuth2AuthorizationRequest
                //                // 提取回调参数code, state - OAuth2AuthorizationResponse
                //                // new OAuth2LoginAuthenticationToken(clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse))
                //                // 调用OidcAuthorizationCodeAuthenticationProvider（scope包含openid） 或者 OAuth2LoginAuthenticationProvider
                //                // AuthProvider校验state等、调用Token端点获取access_token、调用UserInfo端点获取用户信息、返回OAuth2LoginAuthenticationToken(..., isAuthenticated=true)
                //                // 返回Filter通过HttpSessionOAuth2AuthorizedClientRepository在session中保存客户端授权信息OAuth2AuthorizedClient（clientReg, principalName, accessToken, refreshToken）
                //                // 对应session属性名称：org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository..AUTHORIZED_CLIENTS
                //                // 返回用户授权信息OAuth2AuthenticationToken(principal=OidcUser, authorities, clientRegId)
                //                // 对应session属性名称
                //                //===========================================================================================================================
                //                //OAuth2授权中心回调此client的回调uri设置
                //                //在AuthServer完成授权后，AuthServer需要回调此client的baseUri
                //                //实际回调地址/login/oauth2/code/{clientRegId}
                //                //默认值/login/oauth2/code/*
                //                .loginProcessingUrl(OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI)
                //                //此设置会覆盖前面的loginProcessingUrl设置
                //                .redirectionEndpoint(redirection -> redirection
                //                        .baseUri("/login/oauth2/code/*")
                //                )
                //                //OAuth2 Token端点相关HTTP请求设置
                //                .tokenEndpoint(tokenEndpointConfig -> tokenEndpointConfig
                //                        //获取token请求配置
                //                        //Default[AuthorizationCode|ClientCredentials|RefreshToken|JwtBearerToken|Password]TokenResponseClient
                //                        //默认DefaultAuthorizationCodeTokenResponseClient
                //                        .accessTokenResponseClient(new DefaultAuthorizationCodeTokenResponseClient())
                //                )
                //                //OIDC用户端点相关设置
                //                .userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig
                //                        //配置OIDC UserInfo Endpoint HTTP请求
                //                        //默认实现OidcUserService
                //                        .oidcUserService(new OidcUserService())
                //                        //配置OAuth2.0用户查询服务HTTP请求
                //                        //默认实现DefaultOAuth2UserService
                //                        .userService(new DefaultOAuth2UserService())
                //                        //提取用户Authorities
                //                        .userAuthoritiesMapper(authorities -> authorities)
                //                )
                //)
                .oauth2Client(withDefaults())
                //.oauth2Client(oauth2ClientConfigurer -> oauth2ClientConfigurer
                //        .
                //)
                .logout(logout -> logout
                        .invalidateHttpSession(true)
                        //.logoutSuccessUrl("http://oauth2-server:9000/logout")
                        //.logoutSuccessHandler(new OidcEndSessionLogoutSuccessHandler("http://oauth2-server:9000/logout", "http://oauth2-client1:8081/"))
                        .logoutSuccessHandler(oidcClientInitiatedLogoutSuccessHandler)
                )
                //.addFilterBefore(new FrontChannelLogoutGeneratingFilter("front_logout"), DefaultLoginPageGeneratingFilter.class)
        ;
        return http.build();
    }


    /**
     * 配置WebClient支持使用AuthorizedClient
     *
     * @param authorizedClientManager
     * @return
     */
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        //扩展支持PKCE模式（NONE）下刷新token时添加client_id参数
        DefaultRefreshTokenTokenResponseClient refreshTokenTokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
        //设置自定义参数转换器（解决pkce模式下无client_id参数问题）
        refreshTokenTokenResponseClient.setRequestEntityConverter(new OAuth2PkceRefreshTokenGrantRequestEntityConverter());

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        //client支持code流程
                        .authorizationCode()
                        //client支持refresh_token流程
                        .refreshToken(refreshToken -> refreshToken
                                //扩展支持PKCE模式（NONE）下刷新token时添OAuth2AuthorizationRequestRedirectFilter 加client_id参数
                                .accessTokenResponseClient(refreshTokenTokenResponseClient)
                        )
                        .build();
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        //authorizedClientManager.setAuthorizationFailureHandler();

        return authorizedClientManager;
    }

    /**
     * 单点登录配置
     */
    @Bean
    OidcClientInitiatedLogoutSuccessHandler oidcClientInitiatedLogoutSuccessHandler (ClientRegistrationRepository clientRegistrationRepository) {
        OidcClientInitiatedLogoutSuccessHandler oidcClientInitiatedLogoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
        oidcClientInitiatedLogoutSuccessHandler.setPostLogoutRedirectUri("http://oauth2-client1:8081/");
        return oidcClientInitiatedLogoutSuccessHandler;
    }
}